package com.hzw.video.decoder

import android.media.MediaCodec
import android.media.MediaFormat
import android.util.Log
import com.hzw.video.extractor.IExtractor
import java.io.File
import java.nio.ByteBuffer

/**
 *
 * @Author:         HuaZhongWei
 * @CreateDate:     2020/9/15 15:34
 *
 * @Description:
 *
 */
abstract class BaseDecoder(val path: String) : IDecoder {

    companion object {
        const val TAG = "BaseDecoder"
    }

    //-------------线程相关------------------------
    /**
     * 解码器是否在运行
     */
    protected var isRunning = true

    /**
     * 线程等待锁
     */
    private val lock = Object()

    /**
     * 是否可以进入解码
     */
    protected var isReadyForDecode = false

    //---------------解码相关-----------------------
    /**
     * 音视频解码器
     */
    protected var mediaCodec: MediaCodec? = null

    /**
     * 音视频数据读取器
     */
    protected var extractor: IExtractor? = null

    /**
     * 解码输入缓存区
     */
    protected var inputBuffers: Array<ByteBuffer>? = null

    /**
     * 解码输出缓存区
     */
    protected var outputBuffers: Array<ByteBuffer>? = null

    /**
     * 解码数据信息
     */
    private var bufferInfo = MediaCodec.BufferInfo()

    private var state = DecodeState.STOP

    var stateListener: IDecoderStateListener? = null

    /**
     * 流数据是否结束
     */
    private var isEOS = false

    protected var videoWidth = 0

    protected var videoHeight = 0

    private var _duration: Long = 0L
    val duration: Long
        get() = _duration

    var endPos: Long = 0L


    override fun run() {
        state = DecodeState.START
        stateListener?.decoderPrepare(this)

        if (!init()) {
            return
        }

        while (isRunning) {
            if (state != DecodeState.START && state == DecodeState.DECODING && state == DecodeState.SEEKING) {
                Log.i(TAG, "等待解析 ==》 ${state.name}")
                waitDecode()
                startTimeForSync = System.currentTimeMillis() - getCurTimeStamp()
            }

            if (!isRunning || state == DecodeState.STOP) {
                isRunning = false
                break
            }

            if (startTimeForSync == -1L) {
                startTimeForSync = System.currentTimeMillis()
            }

            if (!isEOS) {
                isEOS = pushBufferToDecoder()
            }

            val index = pullBufferFromDecoder()
            if (index >= 0) {
                if (state == DecodeState.DECODING) {
                    sleepRender()
                }

                render(outputBuffers!![index], bufferInfo)

                mediaCodec!!.releaseOutputBuffer(index, true)

                if (state == DecodeState.START) {
                    state = DecodeState.PAUSE
                }
            }

            if (bufferInfo.flags == MediaCodec.BUFFER_FLAG_END_OF_STREAM) {
                state = DecodeState.FINISH
                stateListener?.decoderFinish(this)
            }
        }

        doneDecode()
        release()
    }

    private var startTimeForSync: Long = -1L

    private fun sleepRender() {
        val passTime = System.currentTimeMillis() - startTimeForSync
        val curTime = getCurTimeStamp()
        if (curTime > passTime) {
            Thread.sleep(passTime)
        }
    }

    override fun getCurTimeStamp(): Long {
        return bufferInfo.presentationTimeUs / 1000
    }

    /**
     * 初始化
     */
    private fun init(): Boolean {
        //检查文件是否存在
        if (path.isEmpty() || !File(path).exists()) {
            Log.e(TAG, "文件路径为空或文件不存在")
            stateListener?.decoderError(this, "文件路径为空或文件不存在")
            return false
        }

        //调用虚函数，检查子类参数是否完整
        if (!check()) {
            return false
        }

        //初始化数据提取器
        extractor = initExtractor(path)
        if (extractor == null || extractor!!.getFormat() == null) {
            return false
        }

        //初始化参数
        if (!initParams()) {
            return false
        }

        //初始化渲染
        if (!initRender()) {
            return false
        }

        //初始化解码器
        if (!initCodec()) {
            return false
        }

        return true
    }

    abstract fun check(): Boolean

    abstract fun initExtractor(filePath: String): IExtractor

    private fun initParams(): Boolean {
        try {
            val format = extractor!!.getFormat()!!
            _duration = format.getLong(MediaFormat.KEY_DURATION) / 1000
            if (endPos == 0L) {
                endPos = duration
            }
            initSpecParams(format)
        } catch (e: Exception) {
            e.printStackTrace()
            return false
        }
        return true
    }

    abstract fun initSpecParams(format: MediaFormat)

    abstract fun initRender(): Boolean

    private fun initCodec(): Boolean {
        try {

            //1.根据音视频编码格式初始化解码器
            val type = extractor!!.getFormat()!!.getString(MediaFormat.KEY_MIME)
            mediaCodec = MediaCodec.createDecoderByType(type)
            if (!configCodec(mediaCodec!!, extractor!!.getFormat()!!)) {
                waitDecode()
            }
            //2.配置解码器
            mediaCodec!!.start()

            inputBuffers = mediaCodec?.inputBuffers
            outputBuffers = mediaCodec?.outputBuffers
        } catch (e: Exception) {
            e.printStackTrace()
            return false
        }
        return true
    }

    abstract fun configCodec(mediaCodec: MediaCodec, format: MediaFormat): Boolean


    /**
     * 解码线程进入等待
     */
    private fun waitDecode() {
        try {
            if (state == DecodeState.PAUSE) {
                stateListener?.decoderPause(this)
            }
            synchronized(lock) {
                lock.wait()
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    /**
     * 通知解码线程继续解码
     */
    fun notifyDecode() {
        synchronized(lock) {
            lock.notifyAll()
        }
        if (state == DecodeState.DECODING) {
            stateListener?.decoderRunning(this)
        }
    }

    /**
     * 将数据压入解码器输入缓冲
     */
    private fun pushBufferToDecoder(): Boolean {
        //查询是否有可用的输入缓冲，返回缓冲索引。其中参数2000为等待2000ms，如果填入-1则无限等待。
        val inputBufferIndex = mediaCodec!!.dequeueInputBuffer(2000)
        var isEndOfStream = false

        if (inputBufferIndex >= 0) {
            //通过缓冲索引 inputBufferIndex 获取可用的缓冲区，并使用Extractor提取待解码数据，填充到缓冲区中。
            val inputBuffer = inputBuffers!![inputBufferIndex]
            val sampleSize = extractor!!.readBuffer(inputBuffer)

            if (sampleSize < 0) {
                //如果数据已经取完，压入数据结束标志：BUFFER_FLAG_END_OF_STREAM
                mediaCodec!!.queueInputBuffer(
                    inputBufferIndex,
                    0,
                    0,
                    0,
                    MediaCodec.BUFFER_FLAG_END_OF_STREAM
                )
                isEndOfStream = true
            } else {
                mediaCodec!!.queueInputBuffer(
                    inputBufferIndex,
                    0,
                    sampleSize,
                    extractor!!.getCurrentTimestamp(),
                    0
                )
            }
        }
        return isEndOfStream
    }

    /**
     * 将解码好的数据从缓冲区拉取出来
     */
    private fun pullBufferFromDecoder(): Int {
        // 查询是否有解码完成的数据，index >=0 时，表示数据有效，并且index为缓冲区索引
        //bufferInfo用于获取数据帧信息，第二参数是等待时间，这里等待1000ms，填入-1是无限等待。
        when (val index = mediaCodec!!.dequeueOutputBuffer(bufferInfo, 1000)) {
            //输出格式改变了
            MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> {
            }
            //没有可用数据，等会再来
            MediaCodec.INFO_TRY_AGAIN_LATER -> {
            }
            //输入缓冲改变了
            MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED -> {
                outputBuffers = mediaCodec!!.outputBuffers
            }
            else -> {
                return index
            }
        }
        return -1
    }

    /**
     * 渲染
     */
    abstract fun render(byteBuffer: ByteBuffer, bufferInfo: MediaCodec.BufferInfo)

    /**
     * 结束解码
     */
    abstract fun doneDecode()

    private fun release() {
        try {
            state = DecodeState.STOP
            isEOS = false
            extractor?.stop()
            mediaCodec?.stop()
            mediaCodec?.release()
            stateListener?.decoderDestroy(this)
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }
}